/*
 *  Copyright 2021-2025 Michael T. Richter as Half-Baked Software
 *  This program is free software. It comes without any warranty, to the extent
 *  permitted by applicable law. You can redistribute it and/or modify it under
 *  the terms of the Do What The Fuck You Want To Public License, Version 2, as
 *  published by Sam Hocevar. See the file COPYING or http://www.wtfpl.net/ for
 *  more details.
 */

#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>

#include "co.h"
#include "co_impl.h"
#define OPALLOC_IMPLEMENTATION
#include "opalloc.h"

OP_HL_DECLARE_ALLOCATOR(co_t_impl, 5, OP_DOUBLING_INDIVIDUAL)

#define STACK_CANARY_VALUE 0x5453544c

void *(*co_start)(co_t, void *) = co_resume;

co_t co_create(co_function co_cb, size_t stack_buffer_count, stack_t *stack_store, void *stash)
{
    co_t co = allocate_co_t_impl();

    if (co)
    {
        if (!stack_store)
        {
            co->static_stack = false;
            co->stack_store = malloc(stack_buffer_count * sizeof(stack_t));
        }
        else
        {
            co->static_stack = true;
            co->stack_store = stack_store;
        }

        if (co->stack_store)
        {
            co->stack_canary = NULL;

            co_init_impl(&co->stack_pointer, &co->stack_canary, co_cb, co->stack_store, stack_buffer_count);
            co->stash = stash;

            if (co->stack_canary)
            {
                *co->stack_canary = (stack_t)STACK_CANARY_VALUE;
            }
        }
        else
        {
            deallocate_co_t_impl(co);
            co = NULL;
        }
    }

    return co;
}

void *co_get_stash(co_t co)
{
    return co->stash;
}

void co_destroy(co_t *co)
{
    if (!(*co)->static_stack)
    {
        free((*co)->stack_store);
    }

    deallocate_co_t_impl(*co);
    *co = NULL;
}

void *co_resume(co_t co, void *data)
{
    void *rv;

    if (co_active == co)
    {
        rv = NULL;
    }
    else
    {
        // push previous coroutine's info on the current stack
        co_t co_previous = co_active;
        co_active = co;

        // yield into coroutine
        rv = co_yield_impl(&co->stack_pointer, data);

        // restore previous coroutine's info
        co_active = co_previous;
    }

    return rv;
}

void *co_yield(void *data)
{
    // Permit coroutine-based code to work in non-coroutine environments.
    if (!co_active)
    {
        return NULL;
    }

    // check canary, if this fails a stack overflow occured
    assert(!co_active->stack_canary || *co_active->stack_canary == (stack_t)STACK_CANARY_VALUE);

    // yield out of coroutine
    return co_yield_impl(&co_active->stack_pointer, data);
}
